Skip to content

Conversation

@polypixeldev
Copy link
Member

@polypixeldev polypixeldev commented Nov 12, 2025

Summary of the problem

Currently, contracts are in an OrganizerPosition::Contract model and tightly coupled to OrganizerPositionInvite. With the new application flow I'm working on, we'll need contracts to be abstracted more to support a source from either an Event::Application or OrganizerPositionInvite, since OrganizerPositionInvite will not exist yet when the applicant is signing the contract during the application flow.

Describe your changes

Primarily, this PR adds a new Contract model that is mostly the same as OrganizerPosition::Contract, with a couple differences:

  • Has a polymorphic relationship Contractable instead of a relationship directly to OrganizerPositionInvite. Only OrganizerPositionInvite implements this right now, but in the applications PR Event::Application will also implement it.
  • A type column instead of a purpose column. The purpose column was added in the original implementation of OrganizerPosition::Contract, but it is not currently being used. This PR adds this back as STI, and a future PR will implement better automation for termination contracts

All code that references OrganizerPosition::Contract has been changed to reference Contract instead, including the relations on User and Event. A future PR will drop the OrganizerPosition::Contract model and associated table.

In the database migration that creates this new table, steps have been added that copy over current data from the organizer_position_contracts table and renames item types for versions stored in the PaperTrail versions table.

I've tested this PR using my own DocuSeal account in test mode, but it should be tested with our production DocuSeal account and a real contract before merging into production.

@polypixeldev polypixeldev requested review from a team as code owners November 12, 2025 02:55
Copy link
Member

@sampoder sampoder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm! minus comment on type of contract

Copy link
Member

@garyhtou garyhtou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good schema!

@garyhtou garyhtou enabled auto-merge November 25, 2025 20:28
@garyhtou garyhtou added this pull request to the merge queue Nov 25, 2025
Merged via the queue into main with commit 7931e0a Nov 25, 2025
14 checks passed
@garyhtou garyhtou deleted the polypixeldev/new-contract-model branch November 25, 2025 20:36
github-merge-queue bot pushed a commit that referenced this pull request Nov 26, 2025
## Summary of the problem
<!-- Why are these changes being made? What problem does it solve? Link
any related issues to provide more details. -->

https://appsignal.com/hack-club/sites/6596247683eb67648f30f807/exceptions/incidents/2394

Bug introduced in #12094 - since `Contract` and
`OrganizerPositionInvite` are now decoupled, there are some fields of
the `Contract` model that need to be set using info from the
`Contractable`. Notably, the `prefills` jsonb includes event data that
must be included in the `Contract` before creation, since it is used in
the after create callback that sends the payload to Docuseal. While we
were doing this in the `send_contract` method on
`OrganizerPositionInvite`, we were not doing this when creating a
contract using the create action in `ContractsController` (used when
sending a contract on a pre-existing invite or organizer position),
causing the above bug.

## Describe your changes
<!-- Explain your thought process to the solution and provide a quick
summary of the changes. -->
While this could have been fixed by just grabbing the necessary data
from the `Contractable` and passing it to `Contract.new` in
`ContractsController`, it's better to maintain that contracts should
only be created using methods on the `Contractable`. This way, changes
can be easily made to what data is passed to the `Contract`, and any
extra code (such as updating the `OrganizerPosition` or Airtable can be
done in one place.

This does involve updating the `OrganizerPositionInvite` after it has
been sent and accepted - I've fixed a validation to not fail when this
happens, but it would also be possible to modify the code so that this
model is essentially "frozen" after being accepted, and only update the
`is_signee` field before accepting.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants